<?php
namespace Controller;

use Core\Controller, Core\HHO, Lib, Action\EDM;

class Queue extends Controller {
	//const MONITOR_EAMIL = 'susisoy@163.com,susisoy@qq.com,susisoy@gmail.com,susisoy@sohu.com,susisoy@yahoo.com.cn,susisoy@sina.com';
	const MONITOR_EAMIL = 'susisoy@163.com,susisoy@qq.com';
	const SET_RETRY_TIMEOUT = 7200; // POSTFIX 投递失败，在 POSTFIX 队列中的生存期。期间 POSTFIX 可能会多次重试。生存期结束后仍未成功投递，则删除，且将接收者添加至敏感邮箱列表中。
	const SET_QUEUE_TIMEOUT = 43200; // 一个接收者在发送队列中的生存期。期间可能由于已知原因（比如被列入临时黑名单）导致 POSTFIX 投递失败，且又恢复至发送队列中，并进行了多次重新发送。
	const SET_DISPLAY_HOST = 50; // 向前端报告发送队列中各主机下的邮箱数量排名的前多少名。
	const SET_TIDY_INTERVAL = 60; // 清理发送队列并整理 POSTFIX 队列的间隔时间。
	const SET_SEND_AMOUNT = 10; // 一次 HTTP 请求中完成几个邮箱的发送。	const SET_MONITOR_INTERVAL = 3600; // 向监控邮箱发送邮件的间隔时间。
	private $MEmail = NULL;
	private $MQueue = NULL;

	public function __construct() {
		$this->MEmail = \Model\Email::getInstance();
		$this->MQueue = \Model\Queue::getInstance();
	}

	public function index() {
		$labels = $this->MQueue->getLabel();
		
		foreach ($labels as &$item) {
			$item['sends'] = $this->MQueue->count($item['id'], FALSE);
			$item['count'] = $this->MQueue->count($item['id']);
			$item['waiting'] = $this->MQueue->count($item['id'], NULL, NULL, NULL, NULL, TRUE);
		}
		
		$this->assign('labels', $labels);
		
		$this->display();
	}

	/**
	 * 创建发送队列
	 *
	 * @param string $_REQUEST['label']	队列标识符
	 * @param string $_REQUEST['batch']	参与发送的邮箱批次
	 * @param string $_REQUEST['hosts']	参与发送的邮箱主机列表
	 * @return int
	 */
	public function make() {
		$label = intval($_REQUEST['label']);
		$batch = trim($_REQUEST['batch']);
		$hosts = isset($_REQUEST['hosts']) && $_REQUEST['hosts'] ? explode(',', trim($_REQUEST['hosts'])) : '';
		
		$label && $batch || die("Error: 'label' and 'batch' not empty.");
		
		$email = $this->MEmail->getEmail($batch, $hosts);
		
		$this->MQueue->make($label, $email);
		
		echo $this->MQueue->count($label, NULL, NULL, NULL, NULL, TRUE);
	}

	/**
	 * 队列发送控制台
	 *
	 * @param string $_GET['label']	队列标识符
	 * @return HTML
	 */
	public function queue() {
		if (!isset($_GET['label'])) {
			die('label not empty.');
		}
		
		if (isset($_GET['destroy']) && $_GET['destroy']) {
			$this->MQueue->engineMyISAM();
			
			die();
		}
		
		$cookieKey = 'status_' . $_GET['label'];
		
		if (isset($_COOKIE[$cookieKey])) {
			$x = json_decode($_COOKIE[$cookieKey], TRUE);
			setcookie($cookieKey, json_encode(array('monitoring' => $x['monitoring'])), 0, '/');
		}
		
		$this->MQueue->engineMemory();
		
		$this->assign('label', $_GET['label']);
		$this->assign('bin', HHO_APP_PATH . '/Bin/*.php');
		
		$this->display();
	}

	public function send($amount = 0) {
		$label = intval($_GET['label']);
		
		$cookieKey = 'status_' . $label;
		
		static $status = NULL;
		$status || $status = isset($_COOKIE[$cookieKey]) ? json_decode($_COOKIE[$cookieKey], TRUE) : [];
		
		// ================================================================
		if (!isset($status['tidying']) || $status['tidying'] < time()) {
			$delay = array();
			
			foreach ($this->MQueue->mailq(\Model\Queue::QUEUE_TYPE_ERROR) as $id => $item) {
				$x = explode('@', $item['to']);
				
				if (Lib\String::strpos($item['error'], array('HL:IPB', '550 black ip', 'yahoo.com/421-ts01.html', 'sohu.com/info/policy/9', 'sohu.com/info/policy/10'))) {
					in_array($x[1], $delay) || $delay[] = $x[1];
					
					$this->MQueue->setlive($label, TRUE, $item['to']) !== FALSE && file_put_contents(HHO::$config['runtime.root'] . '/MailQDel.txt', $id . "\n", FILE_APPEND);
				} elseif ($item['time'] < time() - self::SET_RETRY_TIMEOUT) { // 超时从 POSTFIX 发送队列中删除，并标记为可能无效的邮箱。
					$this->MEmail->distrust($item['to'], $item['time']) !== FALSE && file_put_contents(HHO::$config['runtime.root'] . '/MailQDel.txt', $id . "\n", FILE_APPEND);
				}
			}
			
			foreach ($delay as $item) {
				$this->MQueue->settime($label, time() + 1800, $item);
			}
			
			// ================================================================
			$this->MQueue->clean($label, time() - self::SET_QUEUE_TIMEOUT, time() - 600, FALSE);
			
			$hosts = $this->MQueue->hosts($label);
			
			$x = array_intersect_key(EDM::$limit, $hosts);
			$z = array_intersect_key($hosts, EDM::$limit);
			
			$sum = array_sum(array_diff_key($hosts, $x)) + min(array_sum($x), array_sum($z));
			
			if (($sum <= 200 && array_sum($hosts) <= 2000) || $sum <= $this->MQueue->count($label, FALSE, NULL, time() - 120, time()) / 2) {
				$reject = array();
				
				foreach ($hosts as $k => $v) {
					$v > 200 && $reject[] = $k;
				}
				
				$this->MQueue->activate($label, 2000, $reject) && $hosts = $this->MQueue->hosts($label);
			}
			
			$status['hosts'] = array_slice(array_reverse($hosts), 0, self::SET_DISPLAY_HOST);
			
			// ================================================================
			$status['tidying'] = time() + self::SET_TIDY_INTERVAL;
			
			unset($status['count']);
		}
		
		// ================================================================		while (TRUE) {
			$receive = $this->MQueue->receive($label);
			
			if (count($receive) == 0) {
				sleep(1);
				
				return TRUE;
			}
			
			// 在队列中，同一个接收主机的待发量达到一定数量后，暂停向该主机继续发送
			// ================================================================
			$live = 0;
			
			isset($mailq) || $mailq = $this->MQueue->mailq(\Model\Queue::QUEUE_TYPE_LIVE);
			
			if (count($mailq) > 30) { // 队列中的活跃发送数达到一定数量后，暂停新发送插入队列。
				sleep(5);
				
				return TRUE;
			}
			
			foreach ($mailq as $id => $item) {
				$x = explode('@', $item['to']);
				
				if (isset($receive['host']) && $x[1] == $receive['host']) {
					$live++;
					
					unset($mailq[$id]);
				}
			}
			
			if ($live >= 5) {
				$this->MQueue->settime($label, 2, $receive['host']);
				
				continue;
			}
			
			break;
		}
		
		$mailer = $this->MQueue->mailer($label, $receive);
		
		$message = $this->MQueue->message($label, $receive);
		
		// ================================================================		if (!isset($status['monitoring']) || $status['monitoring'] < time()) {
			foreach (explode(',', self::MONITOR_EAMIL) as $item) {
				$this->MQueue->send($mailer, $message, array('prefix' => strtok($item, '@'), 'host' => strtok('@')));
			}
			
			$status['monitoring'] = time() + self::SET_MONITOR_INTERVAL;
		}
		
		$result = $this->MQueue->send($mailer, $message, $receive);
		if ($result) {
			$this->MQueue->settime($label, time(), NULL, $receive['prefix'] . '@' . $receive['host']);
			$this->MQueue->setlive($label, FALSE, $receive['prefix'] . '@' . $receive['host']);
		} else {
			$this->MQueue->settime($label, time() + 300, NULL, $receive['prefix'] . '@' . $receive['host']);
		}
		
		// ================================================================		isset($status['fail']) || $status['fail'] = 0;
		isset($status['history']) || $status['history'] = array();
		
		$result || $status['fail']++;
		
		$status['history'][] = $mailer[4] . ' ' . $receive['host'] . '@' . $receive['prefix'] . ($result ? '' : '<');
		count($status['history']) > 10 && array_shift($status['history']);
		
		if ($result && $amount < self::SET_SEND_AMOUNT - 1) {
			$this->send($amount + 1);
		}
		
		if ($amount > 0) {
			return $result;
		}
		
		// ================================================================		isset($status['succ']) || $status['succ'] = 0;
		$result && $status['succ'] = $this->MQueue->count($label, FALSE);
		
		$status['speed'] = floor($this->MQueue->count($label, FALSE, NULL, time() - 120, time()) / 2);
		
		isset($status['count']) || $status['count'] = $status['succ'] + $this->MQueue->count($label, TRUE);
		
		isset($status['hosts']) || $status['hosts'] = NULL;
		
		// ================================================================
		setcookie($cookieKey, json_encode($status), 0, '/');
		
		echo json_encode($status);
	}

	/**
	 * 从数据库中清空指定列表
	 *
	 * @param string $_GET['label']	队列标识符
	 * @param int $_GET['waiting'] 是否为待发队列
	 * @return int
	 */
	public function clean() {
		$label = intval($_GET['label']);
		$waiting = intval($_GET['waiting']);
		
		$x = $z = 0;
		
		if ($waiting == 0 || $waiting == 2) {
			$x = $this->MQueue->clean($label);
		}
		
		if ($waiting == 0 || $waiting == 1) {
			$z = $this->MQueue->clean($label, NULL, NULL, TRUE);
		}
		
		echo $x + $z;
	}
}